package com.momosw.games.engine;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.momosw.games.engine.events.EndGameEvent;
import com.momosw.games.engine.player.Player;
import com.momosw.games.engine.util.Utils;
import com.momosw.games.engine.util.WallMeasurement;
import com.momosw.games.jericho.board.Board;
import com.momosw.games.jericho.board.Card;
import com.momosw.games.jericho.board.Scoring;
import com.momosw.games.jericho.board.WallCard;
import com.momosw.games.jericho.board.WallType;

/**
 * <p>This represents a game itself.</p>
 * 
 * <p>This class is responsible for:</p>
 * <ul>
 * <li>Managing the players in the game arranging the turn order, giving 
 * the turn to the appropriate player once the current player has played the
 * EndTurnAction.</li>
 * <li>Checking whether the game is over.</li>
 * </ul>
 * 
 * <b>Project:</b> JerichoCardGame<br />
 * <b>Package:</b> com.momosw.games.engine<br />
 * <b>Class:</b> Game.java<br />
 * <br />
 * <i>GSI 2011</i><br />
 *
 * @author Miguel Coronado (miguelcb84@gmail.com)
 * @version	Aug 17, 2011
 *
 */
public class Game {

    /** The players in the game */
    private List<Player> players;
    /** The active player */
    private Player current;
    /** The game configuration */
    private GameConfiguration config;
    /** The board */
    private Board board;
    
    /** 
     * Constructor. Given the players 
     * @param players List of players that will participate in the game
     */
    public Game (GameConfiguration config, List<Player> players) {
        this.players = players;
        this.config = config;
        this.board = new Board (this, players);
        // make sure the player do not have any cards
        for(Player player :  players){
            player.clearHand();
        }
    }
    
    /** 
     * This starts the game. It will assign the turn to the next player 
     * until the game is over according to the method {@link #over()}. 
     */
    public void start () {
        
        beforeStart();
        
        setInitialPlayer();
        while (!this.isOver()){
            new Turn (this, this.current).onStart();
            setNextPlayer();
        }
        
        beforeEnd ();
    }
    
    /**
     * This method is executed just before the game ends
     */
    private void beforeEnd() {
        // The last scoring phase
        Scoring lastScoring = this.board.lastScoring();
        Utils.notifyPlayers(this, lastScoring);

        // Get the result of the game
        try {
            Utils.notifyPlayers(this, new EndGameEvent(getResults()));
        } catch (GameNotOverException e) { /*this wont happen*/ }
    }
    
    /**
     * This method is executed just before the game starts
     */
    private void beforeStart() {
        this.board.getDeck().shuffle();
        for (Player player : this.players) {
            new InitialTurn(this, player).onStart();
        }
        this.board.getDeck().shuffleScoringCards();
    }

    /**
     * Set the initial player
     */
    private void setInitialPlayer() {
        this.current = players.get(0);
    }

    /**
     * Set the next player in the game
     */
    public void setNextPlayer() {
        int current = this.players.indexOf(this.current);
        current = (current+1) % players.size();
        this.current = players.get(current);
    }
    
    /**
     * The players in the game
     * @return List of players
     */
    public List<Player> getPlayers () {
        return players;
    }

    /**
     * The current player
     * @return current player
     */
    public Player getCurrentPlayer () {
        return this.current;
    }

    /**
     * @return the config
     */
    public GameConfiguration getConfig () {
        return config;
    }

    /**
     * 
     * @return
     */
    public Card drawCard () {
        return board.getDeck().pop();
    }
    
    /**
     * 
     * @param card
     */
    public void addSupply (Card card) {
        this.board.getSupply().push(card);
    }

    public Board getBoard() {
        return this.board;
    }

    /**
     * This could be moved to a new action
     */
    public void scoringPhase() {
        // Measure the largest walls
        WallMeasurement measurement = new WallMeasurement(this);

        // Initialize map for counting cards
        Map <WallType, Stack<Card>> supplyCards = new HashMap<WallType, Stack<Card>> ();
        for (WallType wallType : WallType.getTypes()){
            supplyCards.put(wallType, new Stack<Card>());
        }
        
        // Count cards in supply pile
        for (Card card : this.board.getSupply()){
            if (card instanceof WallCard){
                WallType wallType = ((WallCard)card).getWallType();
                Stack<Card> cardsOfAType = supplyCards.get(wallType);
                cardsOfAType.add(card);
                supplyCards.put(wallType, cardsOfAType);
            }
        }
        
        // Distribute points
        Scoring scoring = this.board.setScoring(measurement, supplyCards);
        Utils.notifyPlayers(this, this.current, scoring);
        // Notify status of supply pile 
        Utils.notifyPlayers(this, this.current, this.board.getSupply());
    }

    /**
     * 
     * @return
     */
    public GameResults getResults() throws GameNotOverException{
        if (!isOver()){
            throw new GameNotOverException ("Cannot get the results of a " +
            		"game while it is in play");
        }
        
        return new GameResults (this.board.getScoring());
    }

    /**
     * Determines if the game is over. A Game is over when the last scoring 
     * phase has been archived. According to the original rules a game is over
     * when three scoring phases are played. However, this is a configurable 
     * parameter in the <code>GameConfiguration</code>
     *  
     * @return true if the game is over. false if a new turn must be given.
     */
    public boolean isOver() {
        return board.getScoringCount() >= config.getScoringCount();
    }

}
